package RMI.rtdgTOadmin.impl;

import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import properties.PropertyManager;

import manager.impl.ServiceManager;

import RMI.rtdgTOadmin.IRemoteHostManager;
import RMI.rtdgTOadmin.NetworkService;
import RMI.rtdgTOadmin.RequestResult;
import RMI.rtdgTOadmin.informations.ReceiverInformations;
import enums.State;



public class Receiver implements NetworkService, Runnable{
	
	private HashMap<IRemoteHostManager, String> clients = new HashMap<IRemoteHostManager, String>();

	private ArrayList<Integer> pings  = new ArrayList<Integer>();
	
	private ReceiverInformations informations;
	
	private Registry 					embeddedRegistry ;
	private int cp;
	private Remote   					ownRemote;
	private 	  RequestResult 			requestResult = new RequestResult();
	private	final ScheduledExecutorService 	executorService = 
									Executors.newSingleThreadScheduledExecutor();
	
	public ReceiverInformations getInformations() {
		return informations;
	}
	public void setInformations(ReceiverInformations informations) {
		this.informations = informations;
	}

	private Receiver(){
		embeddedRegistry = null;
		ownRemote = null;
	}
	
	public String endPoint(IRemoteHostManager manager){
		try {
			return manager.getIpAddress()+":"+manager.getPortNumber();
		} catch (RemoteException e) {
			ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverEndPointError"));
		}
		return null;
	}
	
	public void register(IRemoteHostManager manager) throws RemoteException {
		synchronized (this) {
			ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverRegistration")+" "+manager.getIpAddress()+" : "+manager.getPortNumber());
			boolean isExist = false;
			int pos = 0;
			IRemoteHostManager key = null;
			for (IRemoteHostManager i: clients.keySet()) {
				if(clients.get(i).equals(endPoint(manager))){
					isExist = true;
					key = i;
					break;
				}
				else pos++;
			}
			if(!isExist){
				ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverCreation"));
				clients.put(manager, manager.getIpAddress()+":"+manager.getPortNumber());
				pings.add(informations.getPingNumber());
				ServiceManager.table.addHost(manager.getIpAddress(), manager.getPortNumber(), State.UP, informations.getPingNumber());
			}
			else{
				ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverSubstitution"));
				clients.remove(key);
				clients.put(manager, manager.getIpAddress()+":"+manager.getPortNumber());
				pings.remove(pos);
				pings.add(informations.getPingNumber());
				ServiceManager.table.updateHost(manager.getIpAddress(), manager.getPortNumber(),pings.get(pos), State.UP);}
		}
	}
	
	public String getIpAddress(IRemoteHostManager manager){
		String res = clients.get(manager);
		res = res.substring(0, res.indexOf(':'));
		return res;
	}
	
	public int getPortNumber(IRemoteHostManager manager){
		String res = clients.get(manager);
		res = res.substring(res.indexOf(':')+1);
		return new Integer(res);
	}
	
	public void remove(IRemoteHostManager manager, int pos){
		synchronized (this) {
			ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverRemoving"));
			ServiceManager.table.removeHost(getIpAddress(manager), getPortNumber(manager));
			clients.remove(manager);
			pings.remove(pos);
		}
	}
	
	public void unregister(IRemoteHostManager manager) throws RemoteException {
		synchronized (this) {
			boolean found = false;
			IRemoteHostManager key = null;
			for (IRemoteHostManager i: clients.keySet()) {
				if(clients.get(i).equals(endPoint(manager))){
					found = true;
					key = i;
					break;
				}
			}
			if(found){
				ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverUnregistering"));
				ServiceManager.table.removeHost(getIpAddress(key), getPortNumber(key));
				clients.remove(key);
			}
			else ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverClientNotRegistred"));
		}
	}

	public void run() {
		synchronized (this) {
			if ( clients.size() < 1) return;
			System.out.println(clients.size() +" "+PropertyManager.properties.getProperty("connectedRTDG"));
			
			StringBuilder clientMsgBuilder = new StringBuilder(ServiceManager.name);

			requestResult.setSomeThing(clientMsgBuilder.toString());
			cp = -1;
			for(IRemoteHostManager client: clients.keySet()) {
				cp++;
				try {
					client.ping(requestResult);
					//ServiceManager.table.updateHost(client.getIpAddress(), client.getPortNumber(),pings.get(cp), State.UP);
				} catch (RemoteException e) {
					System.out.println("cp: "+cp);
					ServiceManager.table.updateHost(getIpAddress(client), getPortNumber(client),pings.get(cp)-1, State.UNAVAILABLE);
					ServiceManager.printLog(PropertyManager.properties.getProperty("remaininPing")+" "+pings.set(cp, pings.get(cp)-1));
					if(pings.get(cp)==0){
						remove(client, cp);
						break;
					}
				}
			}
		}
	}
	
	public void startListening(){
		String networkServiceBindingName = NetworkService.AdminNetworkServiceBindingName;
		
		int serverPort = informations.getServerPortNumber();
		int registryPort = informations.getRegistryPortNumber();
		
		try {
			ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverRegistryCreation"));
			embeddedRegistry = LocateRegistry.createRegistry(registryPort);
		} catch (RemoteException e) {
			try {
				ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverRegistryCreationError"));
				embeddedRegistry = LocateRegistry.getRegistry(registryPort);
			} catch (RemoteException e1) {
				ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverGetRegistryError"));
			}
		}
		try {
			ownRemote = UnicastRemoteObject.exportObject(this, serverPort);
			embeddedRegistry.bind(networkServiceBindingName, ownRemote);
		} catch (Exception e) {
			ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverError"));
		}
		ServiceManager.printLog(PropertyManager.properties.getProperty("rmiReceiverWaiting"));
		
		executorService.scheduleAtFixedRate(this, informations.getInitialDelay(), informations.getCheckStep(), TimeUnit.SECONDS);
	}
}
